/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2012-2018. All rights reserved.
 * Description: tune for the emmc/sd card
 * Author: wenhao w00470829
 * Create: 2019-03-22
 * Notes: None
 * History: 2019-03-22 wenhao w00470829 first release
 */

#include "mmc_core.h"
#include "sdhci.h"
#include "proc/sdhci_proc.h"

#include "linux/device.h"
#include "linux/platform_device.h"
#include "linux/module.h"

#define PHASE_SCALE         (32)
#define NOT_FOUND           (-1)
#define MAX_TUNING_NUM      (1)
#define SAMP_PHASE_DEF      (15)

#define SAMP_PHASE_ENDPOINT (31)
#define PHASE_SCALE         (32)

void MMC_TuneOut(struct sdhc_host *host, unsigned int start,
                 unsigned int top, unsigned int bottom, unsigned int end)
{
    int phase;
    unsigned int reg;

    if ((unsigned int)NOT_FOUND == top) {
        top = start;
    }

    if ((unsigned int)NOT_FOUND == bottom) {
        bottom = end;
    }
    if (bottom < top) {
        phase = (top + bottom) >> 1;
        phase = phase - (PHASE_SCALE >> 1);
        phase = (phase < 0) ? (PHASE_SCALE + phase) : phase;
    } else {
        phase = (top + bottom) >> 1;
    }
    sdhci_hisi_set_samp_phase(host, phase);

    reg = sdhc_readw(host, SDHC_HOST_CONTROL2);
    reg |= SDHC_CTRL_TUNED_CLK;
    sdhc_writew(host, reg, SDHC_HOST_CONTROL2);

    sdhci_enable_plug_irq(host, host->irq_enable);

    mmc_printf(MMC_PRINTF_INFO, "tuning  valid phase shift [%d, %d] Final Phase %d\n",
               top, bottom, phase);
}

int MMC_ExeTuning(struct mmc_host *mmc, unsigned int opcode)
{
    struct sdhc_host *host = NULL;
    unsigned int index, reg;
    unsigned int error = 0;
    unsigned int found = 0;
    unsigned int startPoint = 0;
    unsigned int endPoint = SAMP_PHASE_ENDPOINT;
    unsigned int prevError = (unsigned int)NOT_FOUND;
    unsigned int raisePoint = (unsigned int)NOT_FOUND;
    unsigned int fallPoint = (unsigned int)NOT_FOUND;

    host = mmc->priv;
    reg = sdhc_readl(host, SDHC_AUTO_TUNING_CTRL);
    reg |= SDHC_SW_TUNING_EN;
    sdhc_writel(host, reg, SDHC_AUTO_TUNING_CTRL);

    reg = host->irq_enable | SDHC_INT_DATA_AVAIL;
    sdhci_enable_plug_irq(host, reg);

    for (index = startPoint; index <= endPoint; index++) {
        sdhci_hisi_set_samp_phase(host, index);
        if (mmc_send_tuning(host->mmc, opcode, NULL) == 0) {
            found = 1;  /* found a valid phase */
        } else {
            error = 1;
        }

        if (index > startPoint) {
            if (error && !prevError) {
                fallPoint = index - 1;
            }
            if (!error && prevError) {
                raisePoint = index;
            }
        }

        if ((raisePoint != (unsigned int)NOT_FOUND) &&
                (fallPoint != (unsigned int)NOT_FOUND)) {
            break;
        }
        prevError = error;
        error = 0;
    }

    if (!found) {
        mmc_err("no valid phase shift! use default\n");
        sdhci_hisi_set_samp_phase(host, SAMP_PHASE_DEF);
    } else {
        mmc_printf(MMC_PRINTF_INFO, "Tuning finished!!\n");
        MMC_TuneOut(host, startPoint, raisePoint, fallPoint, endPoint);
    }
    return 0;
}
